import Canceler from './canceler'
import { RequestOptions, UploadOptions } from './types'
import { queue } from './queue'
import { genUniqueReqKey } from './utils'

const now = () => Date.now()

const appendUrl = (url: string, str: string): string =>
  `${url}${url.includes('?') ? '&' : '?'}${str}`

const stringifyQuery = (query: any) => {
  let result = ''
  const queryArr = Object.keys(query)

  queryArr.forEach((key, idx) => {
    const str = idx === queryArr.length - 1 ? '' : '&'

    result += `${key}=${query[key]}${str}`
  })
  return result
}

export class Request {
  pendingList
  options

  constructor(options) {
    this.pendingList = new Map()
    this.options = options
  }

  _requestInterceptor(config) {
    const { options } = this
    if (config.method === 'GET' && !config.url.includes('_t')) {
      config.url = appendUrl(config.url, stringifyQuery({ _t: now() }))
    }

    if (options.requestInterceptor) {
      config = options.requestInterceptor(config)
    }

    return config
  }

  _responseInterceptor(response) {
    const { options } = this

    if (options.responseInterceptor) {
      response = options.responseInterceptor(response)
    }

    return response
  }

  _fetch<T>(options) {
    const mergedOptions = {
      ...this.options.config,
      ...options
    }
    const uniqueKey = genUniqueReqKey(mergedOptions)

    if (this.pendingList.has(uniqueKey)) {
      return this.pendingList.get(uniqueKey)
    }

    const config = this._requestInterceptor(mergedOptions)

    const promise = new Promise<T>((resolve, reject) => {
      // TODO 将队列代码和基础库解耦，队列判断不应该属于请求
      if (queue.isPending && !options.force) {
        this.pendingList.delete(uniqueKey)
        queue.add(config, resolve, reject)
        return
      }

      this._request(config, resolve, reject, uniqueKey)
    })

    this.pendingList.set(uniqueKey, promise)

    return promise
  }

  _request(config, resolve, reject, uniqueKey = '') {
    config = this._requestInterceptor(config)
    uniqueKey = uniqueKey || genUniqueReqKey(config)
    const url = config.query ? appendUrl(config.url, stringifyQuery(config.query)) : config.url

    const request = config.reqMethod({
      url,
      timeout: config.timeout,
      header: config.headers,
      method: config.method,
      data: config.param,
      name: config.name,
      filePath: config.filePath,
      formData: config.data,
      success: (resp) => {
        let respData = resp.data
        if (typeof respData === 'string') {
          respData = JSON.parse(respData)
        }
        this.options.responseCallback(respData, resolve, reject, config)
      },
      fail(resp) {
        reject(resp)
      },
      complete: () => {
        this.pendingList.delete(uniqueKey)
        Canceler.delete(uniqueKey)
      }
    })

    Canceler.add(uniqueKey, request)
  }

  get<T>(url: string, options?: RequestOptions): Promise<T> {
    return this._fetch<T>({
      ...options,
      url: `${this.options.config.baseURL}${url}`,
      method: 'GET',
      reqMethod: wx.request
    })
  }

  post<T>(url: string, options?: RequestOptions): Promise<T> {
    return this._fetch<T>({
      ...options,
      url: `${this.options.config.baseURL}${url}`,
      method: 'POST',
      reqMethod: wx.request
    })
  }

  upload(options: UploadOptions) {
    return this._fetch({
      ...options,
      url: this.options.config.uploadURL,
      reqMethod: wx.uploadFile
    })
  }
}
